import { useClickAway } from '@/composables/useClickAway'
import { useTouch } from '@/composables/useTouch'
import { preventDefault } from '@/utils'
import { callInterceptor, Interceptor } from '@/utils/interceptor'
import {
	computed,
	defineComponent,
	PropType,
	reactive,
	Ref,
	ref,
	unref
} from 'vue'
import './index.less'

export type SwipeCellSide = 'left' | 'right'
export type SwipeCellPosition = SwipeCellSide | 'cell' | 'outside'

export const range = (num: number, min: number, max: number) => {
	return Math.min(Math.max(num, min), max)
}

export const getWidthByRef = (ref: Ref<HTMLElement | undefined>) => {
	if (ref.value) {
		const element = unref(ref)
		if (element && element.getBoundingClientRect) {
			return element.getBoundingClientRect().width
		}
		return (
			{
				top: 0,
				left: 0,
				bottom: 0,
				right: 0,
				width: 0,
				height: 0
			} as DOMRect
		).width
	}
	return 0
}

export default defineComponent({
	name: 'touch-slide',
	props: {
		name: {
			type: [String, Number],
			default: ''
		},
		beforeClose: Function as PropType<Interceptor>,
		stopPropagation: Boolean,
		hasOpend: Boolean,
		disabled: Boolean
	},
	emits: ['open', 'close', 'click'],
	setup(props, { emit, slots }) {
		let startOffset: number
		let opened: boolean
		let lockClick: boolean

		const root = ref<HTMLElement>()
		const leftRef = ref<HTMLElement>()
		const rightRef = ref<HTMLElement>()

		const state = reactive({
			offset: 0,
			dragging: false
		})

		const leftWidth = computed(() => getWidthByRef(leftRef))
		const rightWidth = computed(() => getWidthByRef(rightRef))

		const touch = useTouch()

		const open = (side: SwipeCellSide) => {
			opened = true
			state.offset = side === 'left' ? leftWidth.value : -rightWidth.value

			emit('open', {
				name: props.name,
				position: side
			})
		}

		const close = (position: SwipeCellPosition) => {
			state.offset = 0

			if (opened) {
				opened = false
				emit('close', {
					name: props.name,
					position
				})
			}
		}

		const toggle = (side: SwipeCellSide) => {
			const offset = Math.abs(state.offset)
			const THRESHOLD = 0.15
			const threshold = opened ? 1 - THRESHOLD : THRESHOLD
			const width = side === 'left' ? leftWidth.value : rightWidth.value

			if (width && offset > width * threshold) {
				open(side)
			} else {
				close(side)
			}
		}

		const onTouchStart = (event: TouchEvent) => {
			if (props.disabled) return
			startOffset = state.offset
			touch.start(event)
		}

		const onTouchMove = (event: TouchEvent) => {
			if (props.disabled) return

			const { deltaX } = touch
			touch.move(event)

			if (touch.isHorizontal()) {
				lockClick = true
				state.dragging = true

				const isEdge = !opened || deltaX.value * startOffset < 0
				if (isEdge) {
					preventDefault(event, props.stopPropagation)
				}

				state.offset = range(
					deltaX.value + startOffset,
					-rightWidth.value,
					leftWidth.value
				)
			}
		}

		const onTouchEnd = () => {
			if (state.dragging) {
				state.dragging = false
				toggle(state.offset > 0 ? 'left' : 'right')

				setTimeout(() => {
					lockClick = false
				}, 0)
			}
		}

		const getClickHanlder =
			(position: SwipeCellPosition, stop?: boolean) => (event: MouseEvent) => {
				if (stop) {
					event.stopPropagation()
				}
				onClick(position)
			}

		const onClick = (position: SwipeCellPosition = 'outside') => {
			emit('click', position)

			if (opened && !lockClick) {
				callInterceptor({
					interceptor: props.beforeClose,
					args: [
						{
							name: props.name,
							position
						}
					],
					done: () => close(position)
				})
			}
		}

		useClickAway(root, () => onClick('outside'), { eventName: 'touchstart' })

		const renderSideContent = (
			side: SwipeCellSide,
			ref: Ref<HTMLElement | undefined>
		) => {
			const contentSlot = slots[side]
			if (contentSlot) {
				return (
					<div
						class={`touch-slide-${side}`}
						ref={ref}
						onClick={getClickHanlder(side, true)}
					>
						{contentSlot()}
					</div>
				)
			}
		}

		return () => {
			const wrapperStyle = {
				transform: `translate3d(${state.offset}px, 0, 0)`,
				transitionDuration: state.dragging ? '0s' : '0.6s'
			}
			return (
				<div
					ref={root}
					class="touch-slide"
					onClick={getClickHanlder('cell')}
					onTouchstart={onTouchStart}
					onTouchmove={onTouchMove}
					onTouchend={onTouchEnd}
				>
					<div class="touch-slide-wrapper" style={wrapperStyle}>
						{renderSideContent('left', leftRef)}
						{slots.default?.()}
						{renderSideContent('right', rightRef)}
					</div>
				</div>
			)
		}
	}
})
